import constants from '@/constants';
import { BaseService } from '@/core/base.service';
import {
  CollectionListDto,
  CollectionOrderDto,
  CollectionOrderQueryDto,
  CollectionQueryDto,
  CollectionReceiveDto,
} from '@/dtos';
import { BillStatusEnum, BillTypeEnum } from '@/enums';
import { compareHash, getCode, halt } from '@/utils';
import { Injectable } from '@nestjs/common';
import { Collection, Prisma } from '@prisma/client';
import dayjs from 'dayjs';
import { random } from 'radash';

@Injectable()
export class CollectionService extends BaseService {
  async order({ id, pass }: CollectionOrderDto, userId: number) {
    return await this.redlock.using(
      [`collection:${userId}`],
      10000,
      {},
      async () => {
        return await this.prisma.$transaction(async (tx) => {
          const user = await tx.user.findUnique({
            where: {
              id: userId,
              isBlockd: false,
              isDeleted: false,
            },
          });
          if (!user) {
            return halt('用户信息异常');
          }
          if (!user.isReal) {
            return halt('请先完成实名认证');
          }
          if (!user.txPass) {
            return halt('请先设置操作密码');
          }
          if (!(await compareHash(pass, user.txPass))) {
            return halt('密码错误');
          }

          const collection = await tx.collection.findUnique({
            where: {
              id,
            },
          });
          if (!collection) {
            return halt('卡册数据异常');
          }
          if (!collection.isTradable) {
            return halt('发售未开启');
          }

          const soldCount = await tx.userHoldCollection.count({
            where: {
              collectionId: id,
            },
          });
          if (soldCount >= Number(collection.limit)) {
            return halt('卡册已售罄');
          }

          const count = await tx.userHoldCollection.count({
            where: {
              collectionId: collection.id,
              userId: user.id,
            },
          });

          if (count > 0) {
            return halt('您已经拥有该卡册');
          }

          const total = Number(collection.price);
          if (Number(user.balance) < total) {
            return halt('余额不足');
          }

          // let camp = null;
          // if (!user?.camp || user?.camp <= 0) {
          //   camp = random(1, 18);
          //   await this.prisma.user.update({
          //     where: {
          //       id,
          //     },
          //     data: {
          //       camp,
          //     },
          //   });
          // } else {
          //   camp = user.camp;
          // }

          await tx.userHoldCollection.create({
            data: {
              collectionId: collection.id,
              userId: user.id,
              total,
            },
          });

          // if (camp > 0) {
          //   const card = await tx.card.findFirst({
          //     where: {
          //       collectionId: collection.id,
          //       no: camp.toString(),
          //     },
          //   });
          //   if (!card) {
          //     return halt('卡牌信息异常');
          //   }
          //   await tx.userHoldCard.create({
          //     data: {
          //       cardId: card.id,
          //       userId: user.id,
          //     },
          //   });
          // }

          await tx.user.update({
            where: {
              id: user.id,
              version: user.version,
            },
            data: {
              version: {
                increment: 1,
              },
              balance: {
                decrement: total,
              },
            },
          });

          await tx.bill.create({
            data: {
              no: await this.generateOrderNo(),
              total,
              fee: 0,
              type: BillTypeEnum.BUY,
              status: BillStatusEnum.COMPLETED,
              ownerId: user.id,
              remark: `购买《${collection.title}》卡册`,
            },
          });

          const owner = await tx.user.findUnique({
            where: {
              id: constants.default.ownerId,
            },
          });

          await tx.user.update({
            where: {
              id: owner.id,
              version: owner.version,
            },
            data: {
              version: {
                increment: 1,
              },
              balance: {
                increment: total,
              },
            },
          });

          await tx.bill.create({
            data: {
              no: await this.generateOrderNo(),
              total,
              fee: 0,
              type: BillTypeEnum.SELL,
              status: BillStatusEnum.COMPLETED,
              ownerId: owner.id,
              remark: `售出《${collection.title}》卡册`,
            },
          });
        });
      },
    );
  }

  async receive({ id, addressId }: CollectionReceiveDto, userId: number) {
    return await this.redlock.using(
      [`collection:receive:${userId}`],
      10000,
      {},
      async () => {
        return await this.prisma.$transaction(async (tx) => {
          const user = await tx.user.findUnique({
            where: {
              id: userId,
              isBlockd: false,
              isDeleted: false,
            },
          });
          if (!user) {
            return halt('用户信息异常');
          }
          if (!user.isReal) {
            return halt('请先完成实名认证');
          }
          const address = await tx.address.findUnique({
            where: {
              id: addressId,
            },
          });
          if (!address) {
            return halt('地址信息异常');
          }
          const collection = await tx.collection.findUnique({
            where: {
              id,
            },
          });
          if (!collection) {
            return halt('卡册信息异常');
          }
          if (
            (await tx.invoiceOrder.count({
              where: {
                collectionId: collection.id,
                ownerId: user.id,
              },
            })) > 0
          ) {
            return halt('请勿重复领取');
          }
          await tx.invoiceOrder.create({
            data: {
              no: await this.generateOrderNo(),
              collectionId: collection.id,
              spu: collection,
              sku: collection,
              address: {
                name: user.realname,
                mobile: user.mobile,
                address: address.address,
                area: address.area,
              },
              ownerId: user.id,
            },
          });
          return true;
        });
      },
    );
  }

  async orders({ params, sort }: CollectionOrderQueryDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    let sql = `
            SELECT #{fields} FROM \`UserHoldCollection\` AS t
            LEFT JOIN \`User\` AS u ON t.userId = u.id
            LEFT JOIN \`Collection\` AS c ON t.collectionId = c.id
            WHERE c.isDeleted = false
              ${!!where.id ? `AND c.id = ${where.id}` : ''}
              ${!!where.total ? `AND t.total = ${where.total}` : ''}
              ${!!where.title ? `AND c.title LIKE '%${where.title}%'` : ''}
              ${!!where.realname ? `AND u.realname LIKE '%${where.realname}%'` : ''}
              ${!!where.mobile ? `AND u.mobile LIKE '%${where.mobile}%'` : ''}
              ${!!where.startTime ? `AND CONVERT_TZ(t.createdAt, 'UTC', 'Asia/Shanghai') >= '${where.startTime}'` : ''}
              ${!!where.endTime ? `AND CONVERT_TZ(t.createdAt, 'UTC', 'Asia/Shanghai') <= '${where.endTime}'` : ''}
        `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(1) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<any[]>(
      `${sql.replace(
        '#{fields}',
        `
            t.userId, t.collectionId, t.total, c.title, c.price, u.realname, u.mobile, t.createdAt         
        `,
      )}
            ORDER BY ${
              !!sort && Object.keys(sort).length > 0
                ? Object.entries(sort)
                    .map(
                      ([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`,
                    )
                    .join(',')
                : 't.createdAt DESC'
            }
            LIMIT ${pageSize} 
            OFFSET ${(current - 1) * pageSize}
        `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o) => ({
        ...o,
        total: Number(o.total),
        price: Number(o.price),
      })),
    };
  }

  async list({ params, sort }: CollectionListDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    let sql = `
            SELECT #{fields} FROM \`Collection\` AS t
            WHERE t.isDeleted = false
                ${!!where.title ? `AND t.title LIKE '%${where.title}%'` : ''}
        `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Collection[]>(
      `${sql.replace(
        '#{fields}',
        `
            t.id, t.title, t.image, t.desc        
        `,
      )}
            ORDER BY ${
              !!sort && Object.keys(sort).length > 0
                ? Object.entries(sort)
                    .map(
                      ([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`,
                    )
                    .join(',')
                : 't.createdAt DESC'
            }
            LIMIT ${pageSize} 
            OFFSET ${(current - 1) * pageSize}
        `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o) => ({
        ...o,
        price: Number(o.price),
        cardPrice: Number(o.cardPrice),
      })),
    };
  }

  async query({ params, sort }: CollectionQueryDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    let sql = `
            SELECT #{fields} FROM \`Collection\` AS t
            WHERE t.isDeleted = false
                ${!!where.title ? `AND t.title LIKE '%${where.title}%'` : ''}
                ${!!where.publisherId ? `AND t.publisherId = ${where.publisherId}` : ''}
                ${!!where.creatorId ? `AND t.creatorId = ${where.creatorId}` : ''}
        `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Collection[]>(
      `${sql.replace(
        '#{fields}',
        `
            t.id, t.title, t.limit, t.price, t.cardPrice, t.isTradable,
            t.image, t.desc, t.detail, t.publisherId, t.creatorId           
        `,
      )}
            ORDER BY ${
              !!sort && Object.keys(sort).length > 0
                ? Object.entries(sort)
                    .map(
                      ([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`,
                    )
                    .join(',')
                : 't.createdAt DESC'
            }
            LIMIT ${pageSize} 
            OFFSET ${(current - 1) * pageSize}
        `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o) => ({
        ...o,
        price: Number(o.price),
        cardPrice: Number(o.cardPrice),
      })),
    };
  }

  async findOne(
    where: Prisma.CollectionWhereUniqueInput,
    select?: Prisma.CollectionSelect,
  ): Promise<Collection | null> {
    return this.prisma.collection.findUnique({
      where,
      select,
    });
  }

  async create(data: Prisma.CollectionCreateInput): Promise<Collection> {
    return await this.prisma.collection.create({
      data: {
        ...data,
      },
    });
  }

  async update(params: {
    where: Prisma.CollectionWhereUniqueInput;
    data: Prisma.CollectionUpdateInput;
  }): Promise<Collection> {
    const { where, data } = params;
    return this.prisma.collection.update({
      where,
      data: {
        ...data,
      },
    });
  }

  async generateOrderNo() {
    return `${dayjs().format('YYYYMMDDHHmmss')}${await getCode(11)}`;
  }
}
